
/*
 * (a)  (C) 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * (b)  If this Sample Code is distributed as part of the Display PostScript
 *	System Software Development Kit from Adobe Systems Incorporated,
 *	then this copy is designated as Development Software and its use is
 *	subject to the terms of the License Agreement attached to such Kit.
 *
 * (c)  If this Sample Code is distributed independently, then the following
 *	terms apply:
 *
 * (d)  This file may be freely copied and redistributed as long as:
 *	1) Parts (a), (d), (e) and (f) continue to be included in the file,
 *	2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * (e)  PostScript, Display PostScript, and Adobe are registered trademarks of
 *	Adobe Systems Incorporated.
 * 
 * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
 *	CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
 *	AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
 *	ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
 *	OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
 *	WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
 *	WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
 *	DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
 *	FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
 *	OF THIRD PARTY RIGHTS.
 */

/*
 *	Bezier.m
 *
 *	The methods here handle the actions necessary to create and draw
 *	a bezier curve. This is the only graphical object in the application.
 *	Some methods found here would probably be moved to a generic
 *	graphical object class in a more general application.  Some methods
 *	have special steps included because of the interface decisions
 *	chosen for a bezier curve. When the first and last control points
 *	are moved, the second and third control points, respectively, are
 *	also moved. This has implications when scrolling and restraining the
 *	drawing to the imagable area.
 *
 *	Version:	2.0
 *	Author:	Ken Fromm
 *	History:
 *			03-07-91		Added this comment.
 */

#import "Bezier.h"
#import "DetectApp.h"
#import "DocView.h"
#import "DrawingView.h"
#import "DrawingViewWraps.h"
#import <appkit/nextstd.h>
#import <dpsclient/dpsclient.h>
#import <dpsclient/wraps.h>

@implementation Bezier

- initFrame:(NXRect *)frm
{
	self = [super init];

	NX_MALLOC(path.pts, float, PTS_BEZIER*2 + 4);
	NX_MALLOC(path.ops, char, 3);

	[self create:frm];

	return self;
}

/* Randomly select the points and calculate the bounds. */
- create:(const NXRect *)frm
{
	int	i;
	
	LLX(path.pts) = LLY(path.pts) = 9999;
	URX(path.pts) = URY(path.pts) = -9999;

	for (i = 0; i < PTS_BEZIER * 2; i += 2)
	{
		path.pts[i + 4] = rand () % ((int)frm->size.width -8) + frm->origin.x + 4;
		path.pts[i + 5] = rand () % ((int)frm->size.height -8) +  frm ->origin.y + 4;

		LLX(path.pts) = MIN(LLX(path.pts), path.pts[i + 4]);
		LLY(path.pts) = MIN(LLY(path.pts), path.pts[i + 5]);
		URX(path.pts) = MAX(URX(path.pts), path.pts[i + 4]);
		URY(path.pts) = MAX(URY(path.pts), path.pts[i + 5]);
	}
	path.num_pts = PTS_BEZIER * 2 + 4;
	
	/*
	*  The first entry is set to dps_setbbox for hit detection but then reset
	*  to dps_ucache when drawing (except when the user is changing
	*  the shape of the object frequently.)
	*/
	path.ops[0] = dps_setbbox;
	path.ops[1] = dps_moveto;
	path.ops[2] = dps_curveto;
	path.num_ops = 3;

	return self;
}

- (UPath *) getPath
{
	return &path;
}

- copyPts:srcId
{
	UPath	*srcPath;
	
	srcPath  = [srcId  getPath];

	NX_MALLOC(path.pts, float, PTS_BEZIER*2 + 4);
	NX_MALLOC(path.ops, char, 3);

	bcopy(srcPath->pts, path.pts, srcPath->num_pts* (sizeof(float)/sizeof(char)));
	bcopy(srcPath->ops, path.ops, srcPath->num_ops);

	return self;
}

/*
*	Returns the bounds.  The flag variable determines whether the
*	knobs should be factored in. They may need to be for drawing but
*	might not if needed for constraining reasons.
*/
- getBounds:(NXRect *)bRect  withKnobs:(BOOL) flag
{
	float		knobsize;

	bRect->origin.x = LLX(path.pts);
	bRect->origin.y = LLY(path.pts);
	bRect->size.width = URX(path.pts) - LLX(path.pts);
	bRect->size.height = URY(path.pts) -LLY(path.pts);

	if (flag)
	{
		knobsize = -[[[NXApp getDrawingView]  superview]  controlPointSize]/2;	
		NXInsetRect(bRect, knobsize, knobsize);
		NXIntegralRect(bRect);
	}

	return self;
}

/* Given the point number, return the point. */
- getPoint:(int) pt_num  :(NXPoint *) pt
{	
	pt->x = path.pts[pt_num*2 + 4];
	pt->y = path.pts[pt_num*2 + 5];
	
	return self;
}

/*
*	Depending on the pt_num passed in, return the rectangle
*	that should be used for scrolling purposes. When the rectangle
*	passes out of the visible rectangle then the screen should
*	scroll. If the first and last points are selected, then the second
*	and third points are included in the rectangle. If the second and
*	third points are selected, then they are used by themselves.
*/
- getScrollRect:(int) pt_num  :(NXRect *) aRect
{
	float			knobsize;

	if (pt_num == -1)
	{
		[self  getBounds:aRect  withKnobs:NO]; 	
	}
	else if (pt_num == 0)
	{	
		aRect->origin.x = MIN(path.pts[4], path.pts[6]);
		aRect->origin.y = MIN(path.pts[5], path.pts[7]);
		aRect->size.width = ABS(path.pts[4] - path.pts[6]);
		aRect->size.height = ABS(path.pts[5] - path.pts[7]);
	}
	else if (pt_num == 3) 
	{
		aRect->origin.x = MIN(path.pts[10], path.pts[8]);
		aRect->origin.y = MIN(path.pts[11], path.pts[9]);
		aRect->size.width = ABS(path.pts[10] - path.pts[8]);
		aRect->size.height = ABS(path.pts[11] - path.pts[9]);
	}
	else
	{
		aRect->origin.x = path.pts[pt_num*2 + 4];
		aRect->origin.y = path.pts[pt_num*2 + 5];
		aRect->size.width = 0;
		aRect->size.height = 0;
	}

	knobsize = -[[[NXApp  getDrawingView]  superview]  controlPointSize]/2;
	NXInsetRect(aRect, knobsize, knobsize);	

	return self;
}

/* 
*	This method constains the point to the bounds of the view passed
*	in. Like the method above, the constaining is dependent on the
*	control point that has been selected.
*/
- constrainPoint:(NXPoint *)aPt  andNumber:(int) pt_num  toView:aView
{
	float			knobsize;

	NXPoint		viewMax;
	
	NXPoint		*thisPt, *nextPt;

	NXRect		viewRect;

	[aView getBounds:&viewRect];
	viewMax.x = viewRect.origin.x + viewRect.size.width;
	viewMax.y = viewRect.origin.y + viewRect.size.height;

	if (pt_num == 0 || pt_num == 3)
	{
		thisPt = (NXPoint *) &path.pts[pt_num*2 + 4];
		if (pt_num == 0)
			nextPt = (NXPoint *) &path.pts[6];
		else
			nextPt = (NXPoint *) &path.pts[8];

		if (thisPt->x  >  nextPt->x)
			viewRect.origin.x += thisPt->x  -  nextPt->x;
		else
			viewMax.x -= nextPt->x  -  thisPt->x;

		if (thisPt->y  >  nextPt->y)
			viewRect.origin.y += thisPt->y - nextPt->y;
		else
			viewMax.y -= nextPt->y - thisPt->y;
	}

	viewMax.x -= MARGIN;
	viewMax.y -= MARGIN;
	viewRect.origin.x += MARGIN;
	viewRect.origin.y += MARGIN;

	aPt->x = MAX(viewRect.origin.x, aPt->x);
	aPt->y = MAX(viewRect.origin.y, aPt->y);

	aPt->x = MIN(viewMax.x, aPt->x);	
	aPt->y = MIN(viewMax.y, aPt->y);

	return self;
}

/*
 *	Change the point number passed in by the amount passed in in pt.
 *	Recalculate the bounds because one of the bounding points could
 *	have been the changed point.
 */
- changePoint:(int) pt_num  :(const NXPoint *) pt
{
	int		i;
	
	path.pts[pt_num *2 + 4] += pt->x;
	path.pts[pt_num *2 + 5] += pt->y;

	LLX(path.pts) = LLY(path.pts) = 9999;
	URX(path.pts) = URY(path.pts) = -9999;
	for (i = 0; i < PTS_BEZIER * 2; i += 2)
	{
		LLX(path.pts) = MIN(LLX(path.pts), path.pts[i + 4]);
		LLY(path.pts) = MIN(LLY(path.pts), path.pts[i + 5]);
		URX(path.pts) = MAX(URX(path.pts), path.pts[i + 4]);
		URY(path.pts) = MAX(URY(path.pts), path.pts[i + 5]);
	}

	return self;	
}

/*
*	pt_num is the changing control point. pt holds the relative change in each coordinate. 
*	The relative is needed and not the absolute because the closest inside control point
*	changes when one of the outside points change.
*/
- setPoint:(int) pt_num  :(const NXPoint *) pt
{	
	[self changePoint:pt_num :pt];
	
	if (pt_num == 0)
		[self changePoint:1 :pt];
	else if (pt_num == 3)
		[self changePoint:2 :pt];
	
	return self;
}

/* The pt argument holds the relative point change. */
- moveAll:(const NXPoint *) pt
{
	int	i;

	for (i = 0; i < PTS_BEZIER * 2;  i += 2)
	{
		path.pts[i + 4] +=pt->x;
		path.pts[i + 5] += pt->y;	
	}

	LLX(path.pts) += pt->x;
	LLY(path.pts) += pt->y;
	URX(path.pts) += pt->x;
	URY(path.pts) += pt->y;
	
	return self;
}

/*
*	Check for a control point hit. No need to perform the hit detection in
*	the server since its a simple rectangle intersection check. Return the
*	point number hit in the pt_num argument.
*/
- (BOOL) hitControl:(const NXRect *)hitRect :(int *) pt_num  :(float) controlsize
{
	int		i;

	NXRect	knobRect;
	
	knobRect.size.width = knobRect.size.height = controlsize;
	for (i=0; i < PTS_BEZIER*2; i += 2)
	{
		knobRect.origin.x = path.pts[i + 4] - controlsize/2;
		knobRect.origin.y = path.pts[i + 5] - controlsize/2;
		if (NXIntersectsRect(hitRect, &knobRect))
		{
			*pt_num = i/2;
			return YES;
		}
	}

	return NO;
}

/*
 *  Check for hit dectection on the object. This uses the
 *  inustroke operator to check for an intersection of the
 *  hit detection rectangle with the path of the Bezier.
 */
- (BOOL) hitObject:(UPath *) hitUpath
{
	int		hit = NO;

	NXRect	aRect, bRect;
	
	NXSetRect(&aRect, hitUpath->pts[0], hitUpath->pts[1],
			hitUpath->pts[2] - hitUpath->pts[0],
		 	hitUpath->pts[3] - hitUpath->pts[1]);
	[self getBounds:&bRect  withKnobs:NO];
	if (NXIntersectsRect(&aRect, &bRect))
	{
		path.ops[0] = dps_setbbox;		
		PSWHitPath(hitUpath->pts, hitUpath->num_pts, hitUpath->ops, hitUpath->num_ops,
			path.pts, path.num_pts, path.ops, path.num_ops, &hit);
	}
	
	return (BOOL) hit;
}

/*
*	Place the point locations and the chararacters for the control
*	points into the user path description passed in. In this case, the
*	xyshow operator is used instead of a user path. But because the
*	xyshow operator takes the same data format as the user path,
*	the buffers for the user paths are used.  The position of the point
*	is calculated relative to the position of the previous point. If
*	this is the first set of points in the description then put the
*	absolute values in place, otherwise use the lastPoint 
*	argument to calculate the displacement.
*/
- putControlUPath:(UPath *)drawUpath  forRect:(NXRect *)r  :(NXPoint *) lastPoint  
{
	int		i, j;

	NXRect		bounds;

	[self getBounds:&bounds  withKnobs:YES];
	if (!r || NXIntersectsRect(r, &bounds))
	{
		i = drawUpath->num_ops;
		drawUpath->ops[i++] = 'a';
		drawUpath->ops[i++] = 'b';
		drawUpath->ops[i++] = 'b';
		drawUpath->ops[i++] = 'a';
		drawUpath->num_ops += PTS_BEZIER;

		i = drawUpath->num_pts;
		if (i == 0)
		{		
			drawUpath->pts[i++] = path.pts[4];
			drawUpath->pts[i++] = path.pts[5];
		}
		else
		{
			drawUpath->pts[i++] = path.pts[4] - lastPoint->x;
			drawUpath->pts[i++] = path.pts[5] - lastPoint->y;
		}

		for (j = 2; i <  PTS_BEZIER * 2;  j++, i++)
			drawUpath->pts[i] = path.pts[j + 4] - path.pts[j + 2];

 		drawUpath->num_pts += PTS_BEZIER*2;
		lastPoint->x = path.pts[10];
		lastPoint->y = path.pts[11];
	}
	
	return self;
}

/*
*	Place the description of the control lines into the the user path.
*	Update the bounding box of the user path if necessary.
*/
- putControlLinesUPath:(UPath *) drawUpath  forRect:(NXRect *) r
{
	int	i;

	NXRect		bounds;

	[self getBounds:&bounds  withKnobs:YES];
	if (!r || NXIntersectsRect(r, &bounds))
	{
		i = drawUpath->num_ops;
		drawUpath->ops[ i++] = dps_moveto;
		drawUpath->ops[ i++] = dps_lineto;
		drawUpath->ops[ i++] = dps_moveto;
		drawUpath->ops[ i++] = dps_lineto;
		drawUpath->num_ops += 4;

		for (i=0; i < PTS_BEZIER * 2; i++)
			drawUpath->pts[drawUpath->num_pts + i] = path.pts[i + 4];
		drawUpath->num_pts += PTS_BEZIER*2;

		drawUpath->pts[0] = MIN(LLX(path.pts), drawUpath->pts[0]);
		drawUpath->pts[1] = MIN(LLY(path.pts), drawUpath->pts[1]);
		drawUpath->pts[2] = MAX(URX(path.pts), drawUpath->pts[2]);
		drawUpath->pts[3] = MAX(URY(path.pts), drawUpath->pts[3]);
	}
			
	return self;
}

/*
 *	Draws the graphic if it lies within the bounds of the rectangle passed in.
 *	Draws with the ucache on if uFlag is YES.
 */
- drawObject:(NXRect *)r  withUcache:(BOOL)uFlag
{
	int			start_pt = 1;

	NXRect		bounds;

	[self getBounds:&bounds  withKnobs:NO];
	if (!r || NXIntersectsRect(r, &bounds))
	{
		PSsetgray(COLOR); 
		PSsetlinewidth(WIDTH);
		if (uFlag)
		{
			path.ops[0] = dps_ucache;
			start_pt = 0;
		}
		DPSDoUserPath(&path.pts[4], path.num_pts-4, dps_float,
			&path.ops[start_pt], path.num_ops - start_pt, path.pts, dps_ustroke);
	}

	return self;
}

@end
